#include <gtk/gtk.h>
#include <glade/glade.h>
#include <gladeui/glade.h>
#include <string.h>
#include <jni.h>
#include "gtkwire_signal.h"
#include "gtkwire_app.h"
#include "gtkwire_utils.h"
#include "gtkwire_databank.h"

static void 		sig_sender			(GtkWidget *widget,  gpointer data);

static jobject 		message_obj			(JNIEnv *env, gint type, gpointer msg, gpointer msg2);
static GCallback 	get_sig_func			(const gchar *signal_name);

/* pointer to vmachine */
static JavaVM *jvm = NULL;

/* Java object that gets callbacks, "this" */
static jobject glade_app = 0;

/* Class Objects */
static jclass glade_msg_cls = 0;
static jclass integer_cls = 0;
static jclass double_cls = 0;
static jclass string_cls = 0;
static jclass boolean_cls = 0;

/* Method ids */
static jmethodID glade_msg_constr_mid = 0;
static jmethodID integer_constr_mid = 0;
static jmethodID double_constr_mid = 0;
static jmethodID msg_listener_mid = 0;
static jmethodID string_constr_mid = 0;
static jmethodID boolean_constr_mid = 0;

/***********************************************************************/
/*** INITIALIZING                                                    ***/
/***********************************************************************/
void 
init_sig_receiver (JNIEnv *env, jobject caller_glade_app)
{
	/* Initialize the jvm global variable */
	(*env)->GetJavaVM (env, &jvm);

	/* Get global ref from caller glade app */
	glade_app = (*env)->NewGlobalRef (env, caller_glade_app);

	/* Get class obejcts and make global refs */
	jclass glade_msg_cls_l = (*env)->FindClass (env, "gtkwire/GTKWireMessage");
	glade_msg_cls = (*env)->NewGlobalRef (env, glade_msg_cls_l );
	jclass integer_cls_l = (*env)->FindClass (env, "java/lang/Integer");
	integer_cls = (*env)->NewGlobalRef (env, integer_cls_l);
	jclass double_cls_l = (*env)->FindClass (env, "java/lang/Double");
	double_cls = (*env)->NewGlobalRef (env, double_cls_l );
	jclass string_cls_l = (*env)->FindClass (env, "java/lang/String");
	string_cls = (*env)->NewGlobalRef (env, string_cls_l );
	jclass boolean_cls_l = (*env)->FindClass (env, "java/lang/Boolean");
	boolean_cls = (*env)->NewGlobalRef (env, boolean_cls_l);

	/* Get class object for env */
	jclass caller_cls = (*env)->GetObjectClass (env, caller_glade_app);

	/* Get java method ids for message constructors */
	glade_msg_constr_mid = (*env)->GetMethodID (env, glade_msg_cls_l, "<init>",
		"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)V");
	integer_constr_mid = (*env)->GetMethodID (env, integer_cls, "<init>", "(I)V");
	double_constr_mid = (*env)->GetMethodID (env, double_cls, "<init>", "(D)V");
	string_constr_mid = (*env)->GetMethodID (env, string_cls, "<init>", "(Ljava/lang/String;)V");
	boolean_constr_mid = (*env)->GetMethodID (env, boolean_cls, "<init>", "(Z)V");
	msg_listener_mid = (*env)->GetMethodID (env, caller_cls, "gtkwireMessage", "(Lgtkwire/GTKWireMessage;)V");
}

/***********************************************************************/
/*** SIGNAL CONNECTOR FUNCTIONS                                      ***/
/***********************************************************************/
void
signal_connector (	const gchar *handler_name,
			GObject *object,
			const gchar *signal_name,
			const gchar *signal_data,
			GObject *connect_object,
			gboolean after,
			gpointer user_data)
{
	GCallback sig_func = G_CALLBACK (sig_sender);

	if (!after) 	g_signal_connect (	
				G_OBJECT (object),
				signal_name,
				sig_func,
				strdup(signal_name));
	else 		g_signal_connect_after (	
				G_OBJECT (object),
				signal_name,
				sig_func,
				strdup(signal_name));
}

/*
* Connects user created GTK+ widget with specified signal.
*/
void connect_widget (const gchar *widget_name, const gchar *signal_name)
{
	GCallback sig_func = G_CALLBACK (sig_sender);

	/* Get widget */
	GtkWidget *widget;
	widget = get_widget (widget_name);

	/* Connect */
	g_signal_connect (	G_OBJECT (widget),
				signal_name,
				sig_func,
				(gpointer) signal_name);
}


/***********************************************************************/
/*** SIGNAL SENDER FUNCTIONS                                         ***/
/***********************************************************************/
/* Sends glade signal or gtk signal to java app. */
void
send_glade_signal (GtkWidget *widget, const gchar *signal_name, gint msg_type, gpointer msg_p )
{
	send_glade_signal_two_msg (widget, signal_name, msg_type, msg_p, NULL);
}
void
send_glade_signal_two_msg (GtkWidget *widget, const gchar *signal_name, gint msg_type, gpointer msg_p, gpointer msg_p2 )
{
	/* Get env for client jvm. */
	JNIEnv *env;
	if ((*jvm)->AttachCurrentThread (jvm, (void **)&env, NULL) != 0)
	{
		/* some error handling and return? */
		printf("jvm attach error");
	}

	/* Get widget name and type and get java strings from them */
	const gchar* widget_type;
	const gchar* widget_name;

	/* Called from GObject command*/
	if (widget == NULL)
	{
		widget_name = MAGIC_NAME;
		widget_type = GOBJECT_WIDGET_TYPE;
	}
	/* Called from GtkWidget command*/
	else
	{
		widget_type = g_type_name ( G_OBJECT_TYPE(widget));
		widget_name = gtk_widget_get_name (widget);
	}

	jstring wtype = (*env)->NewStringUTF (env,widget_type);//release?
	jstring wname = (*env)->NewStringUTF (env,widget_name);
	jstring sname = (*env)->NewStringUTF (env,signal_name);

	/* Get java message object and make GladeMessage using  it and other data */
	jobject msg_obj = message_obj (env, msg_type, msg_p, msg_p2);
	jobject java_msg = (*env)->NewObject (env, glade_msg_cls, glade_msg_constr_mid, wtype, wname, sname, msg_type, msg_obj);

	/* Send message */
	(*env)->CallVoidMethod (env, glade_app, msg_listener_mid, java_msg);
}

/* Converts message object to java object. */
static jobject
message_obj (JNIEnv *env, gint type, gpointer msg, gpointer msg2 )
{
	gdouble *gd;
	gboolean *b;
	gint *ip;
	gint len;
	jdouble jdoub;
	jboolean jbool;
	jint ji;
	int i;
	const gchar *cstr;

	switch( type )
	{
		case M_DOUBLE:
			gd = (gdouble*) msg;
			jdoub = *gd;
			return (*env)->NewObject (env, double_cls, double_constr_mid, jdoub);

		case M_INT:
			ip = (gint*) msg;
			ji = *ip;
			return (*env)->NewObject (env, integer_cls, integer_constr_mid, ji);

		case M_INT_ARR:
			ip = (gint*) msg2;
			len = *ip;
			jintArray i_arr = (*env)->NewIntArray (env, len);
			ip = (gint*) msg;
			(*env)->SetIntArrayRegion (env, i_arr, 0, len, (jint *) ip);
			return i_arr;

		case M_DOUBLE_ARR:
			ip = (gint*) msg2;
			len = *ip;
			jdoubleArray d_arr = (*env)->NewDoubleArray (env, len);
			gd = (gdouble*) msg;
			(*env)->SetDoubleArrayRegion (env, d_arr, 0, len, (jdouble *) gd);
			return d_arr;

		case M_BOOLEAN:
			b = (gboolean*) msg;
			jbool = *b;
			return (*env)->NewObject (env, boolean_cls, boolean_constr_mid, jbool);

		case M_STR_ARR:
			ip = (gint*) msg2;
			len = *ip;
			jobjectArray str_arr = (*env)->NewObjectArray (env, len, string_cls,  NULL);
			for (i = 0; i < len; i++)
			 	(*env)->SetObjectArrayElement(env, str_arr, i,
					(*env)->NewStringUTF (
						env, read_str_from_glist (msg, i)));

			g_list_free (msg);
			return str_arr;

		case M_NO_OBJECT:
			return NULL;

		case M_STRING:
			cstr = (const gchar*) msg;
			return (*env)->NewStringUTF (env, cstr);
	}
	
	return NULL;
}

static void 
sig_sender (GtkWidget *widget,  gpointer data)
{
	const char *sig = data;
	send_glade_signal (widget, sig, M_NO_OBJECT, NULL);
}
